home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / lisp / clue.lha / clue / examples / menu.l
Lisp/Scheme  |  1989-07-12  |  38KB  |  989 lines

  1. ;;; -*- Mode:Lisp; Package:USER; Base:10; Lowercase:T; Syntax:Common-Lisp -*-
  2.  
  3.  
  4. ;;;----------------------------------------------------------------------------------+
  5. ;;;                                                                                  |
  6. ;;;                          TEXAS INSTRUMENTS INCORPORATED                          |
  7. ;;;                                   P.O. BOX 2909                                  |
  8. ;;;                                AUSTIN, TEXAS 78769                               |
  9. ;;;                                                                                  |
  10. ;;;                Copyright (C) 1989 Texas Instruments Incorporated.                |
  11. ;;;                                                                                  |
  12. ;;; Permission is granted to any individual or institution to use, copy, modify, and |
  13. ;;; distribute this software, provided that  this complete copyright and  permission |
  14. ;;; notice is maintained, intact, in all copies and supporting documentation.        |
  15. ;;;                                                                                  |
  16. ;;; Texas Instruments Incorporated provides this software "as is" without express or |
  17. ;;; implied warranty.                                                                |
  18. ;;;                                                                                  |
  19. ;;;----------------------------------------------------------------------------------+
  20.  
  21.  
  22.  
  23. ;;;----------------------------------------------------------------------------+
  24. ;;;                                                                            |
  25. ;;; Source code for CLUE examples described in Explorer X Window System        |
  26. ;;; Programmer's Reference Manual.                                             |
  27. ;;;                                                                            |
  28. ;;;----------------------------------------------------------------------------+
  29.  
  30.  
  31.  
  32. (unless (find-package 'clue-examples)
  33.   (make-package 'clue-examples
  34.         :use '(lisp xlib clos cluei)))
  35.  
  36. (in-package 'clue-examples :use '(lisp xlib clos cluei))
  37.  
  38.  
  39. ;;;----------------------------------------------------------------------------+
  40. ;;;                                                                            |
  41. ;;;                                   Menu                                     |
  42. ;;;                                                                            |
  43. ;;;----------------------------------------------------------------------------+
  44.  
  45. (defcontact menu (override-shell)
  46.   ()
  47.   (:resources
  48.     (font       :type     font)
  49.     (foreground :type     pixel)
  50.     (title      :type     string)
  51.        
  52.     (state      :initform :withdrawn))
  53.   
  54.   (:documentation
  55.     "Presents a column of menu items."))
  56.  
  57. ;;                               Initialization
  58.  
  59. (defmethod initialize-instance :after ((menu menu) &key title font foreground background &allow-other-keys)
  60.   ;; Create title-frame containing choices child to manage menu items
  61.   (let* ((title   (make-contact
  62.                     'title-frame
  63.                     :parent     menu
  64.                     :name       :title
  65.                     :text       title
  66.                     :font       font
  67.                     :foreground foreground
  68.                     :background (or background :white)))
  69.          
  70.          (manager (make-contact
  71.                     'choices
  72.                     :parent       title
  73.                     :name         :manager
  74.                     :border-width 0)))
  75.  
  76.     ;; Define callback to handle effect of selection
  77.     (add-callback manager :select 'popup-menu-select menu)
  78.  
  79.     ;; Moving pointer off menu causes nil selection
  80.     (add-event manager
  81.                '(:leave-notify :ancestor :nonlinear)
  82.                '(choice-select nil))))
  83.  
  84.  
  85.  
  86. ;;                      :Leave-Notify Event Specifications
  87.  
  88. (defun leave-check (event-key &rest kinds)
  89.   (dolist (kind kinds)
  90.     (unless (member kind '(:ancestor :virtual :inferior :nonlinear :nonlinear-virtual))
  91.       (error "~s isn't a valid kind of ~s event" kind event-key)))
  92.   (list 'leave-match kinds))
  93.  
  94. (defun leave-match (event kinds)
  95.   (member (slot-value event 'kind) kinds :test #'eq))
  96.  
  97. (setf (check-function :leave-notify) 'leave-check)
  98.  
  99.  
  100.  
  101. ;;                               Menu operations
  102.  
  103. (defun menu-manager (menu)
  104.   (title-content (first (composite-children menu))))
  105.  
  106. (defmethod popup-menu-select ((menu menu))
  107.   ;; Pop down immediately
  108.   (setf (contact-state menu) :withdrawn)
  109.   (display-force-output (contact-display menu))
  110.  
  111.   ;; Invoke menu callback
  112.   (apply-callback menu :select))
  113.  
  114. (defun menu-present (menu x y)
  115.   "Present the MENU with the first item centered on the given position."
  116.   ;; Complete initial geometry management before positioning menu
  117.   (unless (realized-p menu)
  118.     (initialize-geometry menu))
  119.  
  120.   (let ((parent  (contact-parent menu))
  121.         (item    (first (composite-children (menu-manager menu)))))
  122.  
  123.     ;; Compute the y position of the center of the first item
  124.     ;; with respect to the menu
  125.     (multiple-value-bind (item-x item-y)
  126.         (contact-translate item 0 (round (contact-height item) 2) menu)
  127.       (declare (ignore item-x))
  128.  
  129.       ;; Try to center first item at the given location, but
  130.       ;; make sure menu is completely visible in its parent  
  131.       (change-geometry
  132.         menu
  133.         :x (max 0 (min (- (contact-width parent) (contact-width menu))
  134.                        (- x (round (contact-width menu) 2))))
  135.         :y (max 0 (min (- (contact-height parent) (contact-height menu))
  136.                        (- y item-y)))
  137.         :accept-p t)))
  138.   
  139.   ;; Make menu visible
  140.   (setf (contact-state menu) :mapped))
  141.  
  142.  
  143. (defun menu-choose (menu x y)
  144.   "Present the MENU at the given location and return the label of the
  145. item chosen. If no item is chosen, then nil is returned."
  146.  
  147.   ;; Set menu callback to return chosen item label
  148.   (add-callback menu :select 'throw-menu-selection menu)
  149.  
  150.   ;; Display the menu so that first item is at x,y.
  151.   (menu-present menu x y)
  152.  
  153.   ;; Event processing loop
  154.   (catch :menu-selection
  155.     (loop (process-next-event (contact-display menu)))))
  156.  
  157. (defun throw-menu-selection (menu)
  158.   "Throw to :menu-selection tag, returning the label of the selected menu button (if any)."
  159.   (let ((selection (choice-selection (menu-manager menu))))
  160.     (throw :menu-selection
  161.            (when selection (button-label selection)))))
  162.  
  163.  
  164.  
  165.  
  166. ;;;----------------------------------------------------------------------------+
  167. ;;;                                                                            |
  168. ;;;                                Title Frame                                 |
  169. ;;;                                                                            |
  170. ;;;----------------------------------------------------------------------------+
  171.  
  172. (defcontact title-frame (composite)
  173.  
  174.   ((font
  175.      :accessor title-font
  176.      :initarg  :font
  177.      :initform "fg-16"
  178.      :type     font)
  179.  
  180.    (foreground
  181.      :accessor title-foreground
  182.      :initarg  :foreground
  183.      :initform :black
  184.      :type     pixel)   
  185.    
  186.    (text
  187.      :accessor title-text
  188.      :initarg  :text
  189.      :type     string)
  190.  
  191.    (compress-exposures
  192.      :allocation :class
  193.      :initform   :on
  194.      :reader     contact-compress-exposures
  195.      :type       (member :off :on)))
  196.   
  197.   (:resources
  198.     font
  199.     foreground
  200.     text
  201.     (event-mask :initform #.(make-event-mask :exposure)))
  202.   
  203.   (:documentation
  204.     "A composite consisting of a text title and another contact."))
  205.  
  206.  
  207.  
  208. ;;                                  Accessors
  209.  
  210. (defmethod (setf title-font) (new-value (title-frame title-frame))
  211.   (title-update title-frame :font (convert title-frame new-value 'font)))
  212.  
  213. (defmethod (setf title-text) (new-value (title-frame title-frame))
  214.   (title-update title-frame :text new-value))
  215.  
  216. (defmethod title-update ((title-frame title-frame) &key text font)
  217.   (with-slots ((current-text text) (current-font font)) title-frame
  218.             
  219.     ;; Update slots
  220.     (setf current-text (or text current-text)
  221.           current-font (or font current-font))
  222.     
  223.     ;; Update geometry
  224.     (when (realized-p title-frame)
  225.       (change-layout title-frame))))
  226.  
  227. (defmethod title-content ((title-frame title-frame))
  228.   (with-slots (children) title-frame
  229.     (first children)))
  230.  
  231.  
  232.  
  233. ;;                             Geometry management
  234.  
  235. (defmethod add-child :before ((title-frame title-frame) child &key)
  236.   (declare (ignore child))
  237.   ;; A title-frame can only have a single content child
  238.   (assert (not (title-content title-frame))
  239.           nil "~s already has a content." title-frame))
  240.  
  241.  
  242. (defmethod manage-geometry ((title-frame title-frame) child x y width height border-width &key)
  243.   (with-slots ((frame-width width) (frame-height height)) title-frame
  244.     
  245.     (let* ((x            (or x            (contact-x child)))
  246.            (y            (or y            (contact-y child)))
  247.            (width        (or width        (contact-width child)))
  248.            (height       (or height       (contact-height child)))
  249.            (border-width (or border-width (contact-border-width child)))
  250.            (total-width  (+ width border-width border-width))
  251.            (total-height (+ height border-width border-width)))
  252.       
  253.       ;; Get preferred frame size for requested content geometry
  254.       (multiple-value-bind (min-width min-height)
  255.           (title-preferred-size-if
  256.             title-frame total-width total-height frame-width frame-height)
  257.         
  258.         ;; Try to ensure at least preferred frame size
  259.         (when
  260.           (or (setf min-width  (when (< frame-width min-width)   min-width))
  261.               (setf min-height (when (< frame-height min-height) min-height)))
  262.           (change-geometry title-frame
  263.                            :width min-width
  264.                            :height min-height
  265.                            :accept-p t)))
  266.       
  267.       ;; Approve request based on current frame size and title size
  268.       (multiple-value-bind (title-width title-height) (title-size title-frame)
  269.         (declare (ignore title-width))
  270.         
  271.         (let ((approved-x      0)
  272.               (approved-y      title-height)
  273.               (approved-width  (- frame-width border-width border-width))
  274.               (approved-height (- frame-height title-height border-width border-width)))
  275.           
  276.           (values
  277.             (and (= x approved-x) (= y approved-y)
  278.                  (= width approved-width) (= height approved-height))
  279.             approved-x
  280.             approved-y
  281.             approved-width
  282.             approved-height
  283.             border-width))))))
  284.  
  285. (defmethod change-layout ((title-frame title-frame) &optional newly-managed)
  286.   (declare (ignore  newly-managed))
  287.   (with-slots (width height) title-frame
  288.  
  289.     ;; Try to ensure at least preferred size
  290.     (multiple-value-bind (min-width min-height) (preferred-size title-frame)
  291.       (when
  292.         (or (setf min-width  (when (< width min-width)   min-width))
  293.             (setf min-height (when (< height min-height) min-height)))
  294.         (change-geometry title-frame
  295.                          :width min-width
  296.                          :height min-height
  297.                          :accept-p t)))
  298.  
  299.     ;; Adjust title, content geometry to current size
  300.     (title-adjust title-frame)))
  301.  
  302.  
  303. (defmethod preferred-size ((title-frame title-frame) &key width height border-width)
  304.   (let ((content (title-content title-frame))
  305.         (width   (or width (contact-width title-frame)))
  306.         (height  (or height (contact-height title-frame))))
  307.  
  308.     ;; Determine total size of content, including border width
  309.     (multiple-value-bind (current-content-width current-content-height)
  310.         (if content
  311.             
  312.             (with-slots ((content-width width)
  313.                          (content-height height)
  314.                          (content-border-width border-width)) content
  315.               (values (+ content-width content-border-width content-border-width)
  316.                       (+ content-height content-border-width content-border-width)))
  317.             
  318.             (values 0 0))
  319.  
  320.       ;; Determine preferred frame size for this content
  321.       (multiple-value-bind (preferred-width preferred-height)
  322.           (title-preferred-size-if
  323.             title-frame current-content-width current-content-height width height)
  324.         
  325.         (values
  326.           preferred-width
  327.           preferred-height        
  328.           (or border-width (contact-border-width title-frame)))))))
  329.  
  330.  
  331. (defun title-preferred-size-if (title-frame content-width content-height width height)
  332.   "Return preferred TITLE-FRAME width and height, assuming given content size and the
  333.    suggested WIDTH and HEIGHT for the TITLE-FRAME."
  334.   
  335.   (multiple-value-bind (title-width title-height)
  336.       (title-size title-frame)
  337.     
  338.     (values
  339.       ;; width
  340.       (max title-width content-width width)
  341.       
  342.       ;; height
  343.       (max (+ title-height content-height) height))))
  344.  
  345.  
  346. (defun title-adjust (title-frame)
  347.   "Rearrange title and content according to current size of TITLE-FRAME."
  348.   (with-slots (width height) title-frame
  349.     (let* ((content      (title-content title-frame))
  350.            (border-width (contact-border-width content)))
  351.  
  352.       ;; Determine dimensions of title string
  353.       (multiple-value-bind (title-width title-height) (title-size title-frame)
  354.         (declare (ignore title-width))
  355.         
  356.         (let ((approved-x      0)
  357.               (approved-y      title-height)
  358.               (approved-width  (- width border-width border-width))
  359.               (approved-height (- height title-height border-width border-width)))
  360.  
  361.           ;; Reposition content
  362.           (with-state (content)
  363.             (when (not (and (= (contact-x content) approved-x)
  364.                             (= (contact-y content) approved-y)))
  365.               (move content approved-x approved-y))
  366.             
  367.             (when (not (and (= (contact-width content) approved-width)
  368.                             (= (contact-height content) approved-height)))
  369.               (resize content approved-width approved-height border-width)))
  370.  
  371.           ;; Redisplay title
  372.           (when (realized-p title-frame)
  373.             (clear-area title-frame :exposures-p t)))))))
  374.  
  375.  
  376. (defun title-size (title-frame)
  377.   "Return the width and height of the title string of the TITLE-FRAME."
  378.   (with-slots (font text) title-frame
  379.     (values
  380.       (text-width font text)
  381.       (+ (font-ascent font) (font-descent font)))))
  382.  
  383. (defun title-position (title-frame)
  384.   "Return the position of the title string of the TITLE-FRAME."
  385.   (with-slots (font text width) title-frame
  386.     (values
  387.       (round (- width (text-width font text)) 2)
  388.       (font-ascent font))))
  389.  
  390. (defmethod resize :after ((title-frame title-frame) width height border-width)
  391.   (declare (ignore width height border-width))
  392.   (title-adjust title-frame))
  393.  
  394.  
  395.  
  396. ;;                                   Display
  397.  
  398. (defmethod display ((title-frame title-frame) &optional x y width height &key)
  399.   (declare (ignore x y width height))
  400.   
  401.   (with-slots (font text foreground background) title-frame
  402.     (multiple-value-bind (title-x title-y) (title-position title-frame)
  403.       
  404.       ;; Draw title string in "reverse-video"
  405.       (using-gcontext (gc :drawable   title-frame
  406.                           :font       font
  407.                           :foreground background
  408.                           :background foreground)       
  409.         (draw-image-glyphs title-frame gc title-x title-y text)))))
  410.  
  411.  
  412.  
  413.  
  414. ;;;----------------------------------------------------------------------------+
  415. ;;;                                                                            |
  416. ;;;                                  Column                                    |
  417. ;;;                                                                            |
  418. ;;;----------------------------------------------------------------------------+
  419.  
  420. (defcontact column (composite) ()
  421.   (:documentation
  422.     "Arranges its children in a vertical column."))
  423.  
  424. (defmethod manage-geometry ((column column) child x y width height border-width &key)
  425.   (with-slots
  426.     ((child-width width)
  427.      (child-height height)
  428.      (child-border-width border-width)
  429.      (child-x x)
  430.      (child-y y))
  431.     child
  432.  
  433.     (let*
  434.       ;; No position change can be approved.
  435.       ((position-approved-p     (not (or (unless (null x) (/= x child-x))
  436.                                          (unless (null y) (/= y child-y)))))
  437.        
  438.        ;; Check if requested size change can be approved.
  439.        (total-width            (+ child-width child-border-width child-border-width))
  440.        (total-height           (+ child-height child-border-width child-border-width))
  441.        (requested-width        (or width child-width))
  442.        (requested-height       (or height child-height))
  443.        (requested-border-width (or border-width child-border-width))
  444.        (new-total-width        (+ requested-width requested-border-width requested-border-width))
  445.        (new-total-height       (+ requested-height requested-border-width requested-border-width)))
  446.  
  447.       ;; Refuse size change immediately if it reduces item size
  448.       (when (or (< new-total-width total-width) (< new-total-height total-height))
  449.         (return-from manage-geometry
  450.           nil
  451.           child-x
  452.           child-y
  453.           (- child-width requested-border-width requested-border-width)
  454.           (- child-height requested-border-width requested-border-width)                 
  455.           requested-border-width))
  456.  
  457.       ;; Approve size change immediately if it does not affect item size
  458.       (when (and (= new-total-width total-width) (= new-total-height total-height))     
  459.         (return-from manage-geometry
  460.           position-approved-p 
  461.           child-x
  462.           child-y
  463.           requested-width
  464.           requested-height
  465.           requested-border-width))
  466.  
  467.       ;; Otherwise, a larger item size has been requested.
  468.       ;; Check if column size can be enlarged sufficiently.
  469.       (multiple-value-bind (column-width column-height)
  470.           (column-preferred-size column new-total-width new-total-height)
  471.  
  472.         ;; Request change to preferred column size
  473.         (multiple-value-bind
  474.           (approved-p approved-x approved-y approved-width approved-height)
  475.             (change-geometry column :width column-width :height column-height)
  476.           (declare (ignore approved-x approved-y))
  477.          
  478.           (when approved-p
  479.             
  480.             ;; Larger column size approved.
  481.             ;; When requested child geometry approved, change column layout to reflect new
  482.             ;; item size(s). Change child size here first before recomputing item layout.
  483.             (when position-approved-p         
  484.               (with-state (child)
  485.                 (resize child requested-width requested-height requested-border-width))
  486.               (change-geometry column :width column-width :height column-height :accept-p t))
  487.             
  488.             (return-from manage-geometry
  489.               position-approved-p 
  490.               child-x
  491.               child-y
  492.               requested-width
  493.               requested-height
  494.               requested-border-width))
  495.           
  496.           ;; Larger column size NOT approved. Return best item size that could fit
  497.           ;; approved column size
  498.           (return-from manage-geometry
  499.             nil
  500.             child-x
  501.             child-y
  502.             (- approved-width requested-border-width requested-border-width)
  503.             (- (floor approved-height (length (composite-children column)))
  504.                requested-border-width requested-border-width)
  505.             requested-border-width))))))
  506.  
  507.  
  508. (defmethod change-layout ((column column) &optional newly-managed)
  509.   (declare (ignore newly-managed))
  510.   (with-slots (width height) column
  511.  
  512.     ;; Compute the maximum preferred size of all children.
  513.     (multiple-value-bind (item-width item-height)
  514.         (column-item-size column)
  515.  
  516.       ;; Compute preferred column size, assuming this item size
  517.       (multiple-value-bind (preferred-width preferred-height)
  518.           (column-preferred-size column item-width item-height)
  519.         
  520.         ;; Try to ensure at least preferred size
  521.         (if
  522.           (or (setf preferred-width  (when (< width preferred-width)   preferred-width))
  523.               (setf preferred-height (when (< height preferred-height) preferred-height)))
  524.           
  525.           ;; Ask parent for larger size
  526.           (change-geometry column
  527.                            :width    preferred-width
  528.                            :height   preferred-height
  529.                            :accept-p t)
  530.           
  531.           ;; Else current size is big enough
  532.           (column-adjust column item-width item-height))))))
  533.  
  534.  
  535. (defmethod preferred-size ((column column) &key new-width new-height new-border-width)
  536.   (with-slots (border-width) column
  537.     (multiple-value-bind (item-width item-height)
  538.         (column-item-size column)       
  539.       (multiple-value-bind (preferred-width preferred-height)
  540.           (column-preferred-size column item-width item-height)
  541.         (values
  542.           (if new-width  (max new-width preferred-width)   preferred-width)
  543.           (if new-height (max new-height preferred-height) preferred-height)
  544.           (or new-border-width border-width))))))
  545.  
  546.  
  547. (defun column-preferred-size (column item-width item-height)
  548.   "Return the preferred width and height for COLUMN, assuming the given
  549. ITEM-WIDTH and ITEM-HEIGHT."
  550.   (with-slots (children) column
  551.     (let ((preferred-margin 8))
  552.       (values
  553.         (+ item-width preferred-margin preferred-margin)
  554.         (+ (* (length children) (+ item-height preferred-margin))
  555.            preferred-margin)))))
  556.  
  557.  
  558. (defun column-item-size (column)
  559.   "Return the maximum preferred width and height of all COLUMN children."
  560.   (with-slots (children) column
  561.     (let ((item-width 0) (item-height 0))
  562.       (dolist (child children)
  563.         (multiple-value-bind (child-width child-height child-bw)
  564.             (preferred-size child)
  565.           (setf item-width  (max item-width  (+ child-width child-bw child-bw))
  566.                 item-height (max item-height (+ child-height child-bw child-bw)))))
  567.       (values item-width item-height))))
  568.  
  569.  
  570. (defun column-adjust (column &optional item-width item-height)
  571.   "Rearrange COLUMN items according to current COLUMN size. If given, ITEM-WIDTH
  572.    and ITEM-HEIGHT define the new size for all items."
  573.   (with-slots (children width height) column
  574.     (when children
  575.       ;; Compute preferred item size, if necessary
  576.       (unless item-height
  577.         (multiple-value-setq (item-width item-height)
  578.           (column-item-size column)))
  579.       
  580.       ;; Compute item spacing
  581.       (let* ((number-items (length children))
  582.              (margin       (max (round (- width item-width)
  583.                                        2)
  584.                                 0))
  585.              (space        (max (round (- height (* number-items item-height))
  586.                                        (1+ number-items))
  587.                                 0)))
  588.         
  589.         ;; Set size and position of each child
  590.         (let ((y 0))
  591.           (dolist (child children)
  592.             (let ((bw (contact-border-width child)))
  593.               (with-state (child)
  594.                 (resize child (- item-width bw bw) (- item-height bw bw) bw) 
  595.                 (move child margin (incf y space))))
  596.             (incf y item-height)))))))
  597.  
  598.  
  599. (defmethod resize :after ((column column) width height border-width)
  600.   (declare (ignore width height border-width))
  601.   (column-adjust column))
  602.  
  603.  
  604. ;;;----------------------------------------------------------------------------+
  605. ;;;                                                                            |
  606. ;;;                                  Choices                                   |
  607. ;;;                                                                            |
  608. ;;;----------------------------------------------------------------------------+
  609.  
  610. (defcontact choices (column)
  611.  
  612.   ((selection
  613.      :reader   choice-selection
  614.      :initform nil
  615.      :type     (or null contact)))
  616.   
  617.   (:documentation
  618.     "A column of items to choose from."))
  619.  
  620.  
  621. (defmethod add-child :after ((choices choices) child &key)
  622.   ;; Initialize child's :select callback
  623.   (add-callback child :select 'choice-select choices child))
  624.  
  625.  
  626. (defmethod choice-select ((choices choices) child)
  627.   ;; Record current selection
  628.   (with-slots (selection) choices
  629.     (setf selection child))
  630.  
  631.   ;; Invoke selection callback
  632.   (apply-callback choices :select))
  633.  
  634.  
  635.  
  636.  
  637. ;;;----------------------------------------------------------------------------+
  638. ;;;                                                                            |
  639. ;;;                                  Button                                    |
  640. ;;;                                                                            |
  641. ;;;----------------------------------------------------------------------------+
  642.  
  643. (defcontact button (contact)
  644.   
  645.   ((label
  646.      :accessor   button-label
  647.      :initarg    :label
  648.      :initform   ""
  649.      :type       string)
  650.  
  651.    (font
  652.      :accessor   button-font
  653.      :initarg    :font
  654.      :initform   "fg-16"
  655.      :type       font)
  656.  
  657.    (foreground
  658.      :accessor   button-foreground
  659.      :initarg    :foreground
  660.      :initform   :black
  661.      :type       pixel)
  662.  
  663.    (compress-exposures
  664.      :allocation :class
  665.      :initform   :on
  666.      :reader     contact-compress-exposures
  667.      :type       (member :off :on)))
  668.   
  669.   (:resources
  670.     (background :initform :white)
  671.     (border     :initform :white)
  672.     font
  673.     foreground
  674.     label)
  675.   
  676.   (:documentation
  677.     "Triggers an action."))
  678.  
  679.  
  680.  
  681.  
  682. ;;                                   Display
  683.  
  684. (defmethod display ((button button) &optional x y width height &key)
  685.   (declare (ignore x y width height))
  686.   
  687.   (with-slots
  688.     (font label foreground (button-width width) (button-height height))
  689.     button
  690.     
  691.     ;; Get metrics for label string
  692.     (multiple-value-bind (label-width ascent descent left right font-ascent font-descent)
  693.         (text-extents font label)
  694.       (declare (ignore ascent descent left right))
  695.  
  696.       ;; Center label in button
  697.       (let ((label-x (round (- button-width label-width) 2))
  698.             (label-y (+ (round (- button-height font-ascent font-descent) 2)
  699.                         font-ascent)))
  700.  
  701.         ;; Use an appropriate graphics context from the cache
  702.         (using-gcontext (gc :drawable   button
  703.                             :font       font
  704.                             :foreground foreground)
  705.           (draw-glyphs button gc label-x label-y label))))))
  706.  
  707.  
  708. (defmethod preferred-size ((button button) &key new-width new-height new-border-width)
  709.   (with-slots (font label border-width) button
  710.     
  711.     ;; Get metrics for label string
  712.     (multiple-value-bind (label-width ascent descent left right font-ascent font-descent)
  713.         (text-extents font label)
  714.       (declare (ignore ascent descent left right))
  715.  
  716.       (let* ((margin      2)
  717.              (best-width  (+ label-width margin margin))
  718.              (best-height (+  font-ascent font-descent margin margin)))
  719.  
  720.         ;; Return best geometry for this label
  721.         (values
  722.           (if new-width  (max new-width best-width)   best-width)
  723.           (if new-height (max new-height best-height) best-height)
  724.           (or new-border-width border-width))))))
  725.  
  726.  
  727.  
  728. ;;                                   Actions
  729.  
  730. (defmethod button-select ((button button))
  731.   (apply-callback button :select))
  732.  
  733. (defmethod button-set-highlight ((button button) on-p)
  734.   (with-slots (foreground background) button
  735.     (setf (window-border button) (if on-p foreground background))))
  736.  
  737.  
  738. ;;                             Event translations
  739.  
  740. (defevent button :button-press button-select)
  741. (defevent button :enter-notify (button-set-highlight t))
  742. (defevent button :leave-notify (button-set-highlight nil))
  743.  
  744.  
  745.  
  746. ;;;----------------------------------------------------------------------------+
  747. ;;;                                                                            |
  748. ;;;                                 Utilities                                  |
  749. ;;;                                                                            |
  750. ;;;----------------------------------------------------------------------------+
  751.  
  752. (defun contact-translate (from from-x from-y &optional to)
  753.   "Translate the position given by FROM-X and FROM-Y relative to the FROM contact 
  754. into a position relative to the TO contact. By default, TO is (contact-root FROM).
  755. If FROM and TO are on different screens, then nil is returned."
  756.   (declare (values to-x to-y))
  757.   (if to
  758.       (when (eq (contact-root from) (contact-root to))
  759.         ;; Translate both to position and from position to mutual root coordinate system
  760.         ;; and take difference
  761.         (multiple-value-bind (root-from-x root-from-y) (contact-translate from from-x from-y)
  762.           (multiple-value-bind (root-to-x root-to-y) (contact-translate to 0 0)
  763.             (values (- root-from-x root-to-x) (- root-from-y root-to-y)))))
  764.  
  765.       ;; Translate to root coordinate system
  766.       (do* ((to-x   from-x)
  767.             (to-y   from-y) 
  768.             (from   from                        parent)
  769.             (bw     (contact-border-width from) (contact-border-width from))
  770.             (parent (contact-parent from)       (contact-parent from)))
  771.            ((null parent) (values to-x to-y))
  772.         (incf to-x (+ bw (contact-x from)))
  773.         (incf to-y (+ bw (contact-y from))))))
  774.  
  775.  
  776.  
  777. ;;;----------------------------------------------------------------------------+
  778. ;;;                                                                            |
  779. ;;;                              Demonstrations                                |
  780. ;;;                                                                            |
  781. ;;;----------------------------------------------------------------------------+
  782.  
  783.  
  784. (defun just-say-lisp (host &optional (font-name "fg-16"))
  785.   (let* ((display   (open-contact-display 'just-say-lisp :host host))
  786.          (screen    (contact-screen (display-root display)))
  787.          (fg-color  (screen-black-pixel screen))
  788.          (bg-color  (screen-white-pixel screen))
  789.  
  790.          ;; Create menu
  791.          (menu      (make-contact
  792.                       'menu
  793.                       :parent     display
  794.                       :font       font-name
  795.                       :title      "Please pick your favorite language:"
  796.                       :foreground fg-color
  797.                       :background bg-color))
  798.          (menu-mgr  (menu-manager menu)))    
  799.     
  800.     ;; Create menu items
  801.     (dolist (label '("Fortran" "APL" "Forth" "Lisp"))
  802.       (make-contact
  803.         'button
  804.         :parent     menu-mgr
  805.         :label      label
  806.         :foreground fg-color))
  807.        
  808.     ;; Bedevil the user until he picks a nice programming language
  809.     (unwind-protect
  810.         (loop
  811.           ;; Pop up menu at current pointer position
  812.           (multiple-value-bind (x y) (query-pointer (contact-parent menu))
  813.             (let ((choice (menu-choose menu x y)))
  814.               (when (string-equal "Lisp" choice)
  815.                 (return)))))      
  816.  
  817.       (close-display display))))
  818.  
  819.  
  820. (defun pick-one (host &rest strings)
  821.   (let* ((display  (open-contact-display 'pick-one :host host))         
  822.          (menu     (make-contact 'menu :parent display :title "Pick one:")))    
  823.     
  824.     ;; Create menu items
  825.     (dolist (string strings)
  826.       (make-contact 'button :parent (menu-manager menu) :label string))    
  827.     
  828.     ;; Set menu callback to return chosen item label
  829.     (add-callback menu :select 'throw-menu-selection menu)
  830.     
  831.     ;; Display the menu so that first item is at x,y
  832.     (initialize-geometry menu)
  833.     (multiple-value-bind (x y) (query-pointer (contact-parent menu))
  834.       (menu-present menu x y))
  835.     
  836.     ;; Event processing loop
  837.     (let ((selected (catch :menu-selection
  838.                      (loop (process-next-event display)))))
  839.  
  840.       ;; Close server connection
  841.       (close-display display)
  842.     
  843.       ;; Return selected string
  844.       selected)))
  845.  
  846.  
  847. (defun resource-menu (host menu-name item-defaults &rest buttons)
  848.   (let*
  849.     ((display (open-contact-display 'resource-menu :host host))         
  850.      (menu    (make-contact 'menu :parent display :name menu-name)))    
  851.     
  852.     ;; Create menu items
  853.     (dolist (label buttons)
  854.       (make-contact 'button
  855.             :parent   (menu-manager menu)
  856.             :name     (intern (string label))
  857.             :label    (format nil "~:(~a~)" label)
  858.             :defaults item-defaults))    
  859.     
  860.     ;; Set menu callback to return chosen item label
  861.     (add-callback menu :select 'throw-menu-selection menu)
  862.     
  863.     ;; Display the menu so that first item is at x,y
  864.     (initialize-geometry menu)
  865.     (multiple-value-bind (x y) (query-pointer (contact-parent menu))
  866.       (menu-present menu x y))
  867.     
  868.     ;; Event processing loop
  869.     (let ((selected (catch :menu-selection
  870.               (loop (process-next-event display)))))
  871.       
  872.       ;; Close server connection
  873.       (close-display display)
  874.       
  875.       ;; Return selected string
  876.       selected)))
  877.  
  878.  
  879. (defun beatlemenuia (host &optional defaults)
  880.   (loop
  881.  
  882. ;;;----------------------------------------------------------------------------+
  883. ;;;                                                                            |
  884. ;;;                                 Example 1                                  |
  885. ;;;                                                                            |
  886. ;;;----------------------------------------------------------------------------+
  887.  
  888.     (define-resources
  889.       (* beatles title) "Who is your favorite Beatle?")
  890.     
  891.  
  892. ;;;----------------------------------------------------------------------------+
  893. ;;;                                                                            |
  894. ;;;                                 Example 2                                  |
  895. ;;;                                                                            |
  896. ;;;----------------------------------------------------------------------------+
  897.  
  898.     (format t "~%Buttons are white-on-black ...")
  899.     
  900.     (define-resources (* button foreground) :white
  901.                       (* button background) :black
  902.                       (* button border)     :white)
  903.     
  904.     (format t " Choice is ~a"
  905.             (resource-menu host 'Beatles defaults 'John 'Paul 'George 'Ringo))
  906.     
  907.     (undefine-resources (* button foreground) :white
  908.                         (* button background) :black
  909.                         (* button border) :white)
  910.     (unless (y-or-n-p "~%Continue?") (return))
  911.  
  912.  
  913. ;;;----------------------------------------------------------------------------+
  914. ;;;                                                                            |
  915. ;;;                                 Example 3                                  |
  916. ;;;                                                                            |
  917. ;;;----------------------------------------------------------------------------+
  918.  
  919.     (format t "~%Use font FG-22 everywhere ...")
  920.     
  921.     (define-resources (resource-menu * font) "fg-22")
  922.     
  923.     (format t " Choice is ~a"
  924.             (resource-menu host 'Beatles defaults 'John 'Paul 'George 'Ringo))
  925.     
  926.     (undefine-resources (resource-menu * font) "fg-22")
  927.     (unless (y-or-n-p "~%Continue?") (return))    
  928.  
  929.  
  930. ;;;----------------------------------------------------------------------------+
  931. ;;;                                                                            |
  932. ;;;                                 Example 4                                  |
  933. ;;;                                                                            |
  934. ;;;----------------------------------------------------------------------------+
  935.  
  936.     (format t "~%Use gray background in menu ...")
  937.     
  938.     (define-resources (* beatles * background) .8)
  939.     
  940.     (format t " Choice is ~a"
  941.             (resource-menu host 'Beatles defaults 'John 'Paul 'George 'Ringo))
  942.     
  943.     (undefine-resources (* beatles * background) .8)
  944.     (unless (y-or-n-p "~%Continue?") (return))
  945.  
  946.  
  947. ;;;----------------------------------------------------------------------------+
  948. ;;;                                                                            |
  949. ;;;                                 Example 5                                  |
  950. ;;;                                                                            |
  951. ;;;----------------------------------------------------------------------------+
  952.  
  953.     (format t "~%Only John uses font FG-22, Ringo uses gray background ...")
  954.     
  955.     (define-resources (* John font)        "fg-22"
  956.                       (* Ringo background) "50%gray")
  957.  
  958.     (format t " Choice is ~a"
  959.             (resource-menu host 'Beatles defaults 'John 'Paul 'George 'Ringo))
  960.  
  961.     (undefine-resources (* John font)        "fg-22"
  962.                         (* Ringo background) "50%gray")    
  963.     (unless (y-or-n-p "~%Continue?") (return))
  964.  
  965.  
  966. ;;;----------------------------------------------------------------------------+
  967. ;;;                                                                            |
  968. ;;;                                 Example 6                                  |
  969. ;;;                                                                            |
  970. ;;;----------------------------------------------------------------------------+
  971.  
  972.     (format t "~%Select only with :button-3 ...")
  973.     
  974.     (define-resources (* button event-translations)
  975.                       '(((:button-press :button-3) button-select)
  976.                         ((:button-press :button-1) ignore-action)
  977.                         ((:button-press :button-2) ignore-action)))
  978.     
  979.     (format t " Choice is ~a"
  980.             (resource-menu host 'Beatles defaults 'John 'Paul 'George 'Ringo))
  981.     
  982.     (undefine-resources (* button event-translations)
  983.                       '(((:button-press :button-3) button-select)
  984.                         ((:button-press :button-1) ignore-action)
  985.                         ((:button-press :button-2) ignore-action)))
  986.     (unless (y-or-n-p "~%Continue?") (return))))
  987.  
  988.  
  989.